﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IslandUnit.Exceptions;

namespace IslandUnit.Providers
{
    [System.Diagnostics.DebuggerNonUserCode] // No Code Coverage
    internal static class ReflectionHelper
    {
        internal static object New(Type type, params object[] constructorParams)
        {
            return Activator.CreateInstance(type, constructorParams);
        }

        internal static object New<T>(string typeName, params object[] constructorParams)
        {
            Type type = Type.GetType(typeName);

            return New<T>(type, constructorParams);
        }

        internal static object New<T>(Type type, params object[] constructorParams)
        {
            Type[] typeArgs = { typeof(T) };
            Type obj = type.MakeGenericType(typeArgs);

            return Activator.CreateInstance(obj, constructorParams);
        }

        internal static object New(Type genericType1, Type genericType2, string typeName, params object[] constructorParams)
        {
            Type type = Type.GetType(typeName);
            Type[] typeArgs = { genericType1, genericType2 };
            Type obj = type.MakeGenericType(typeArgs);

            return Activator.CreateInstance(obj, constructorParams);
        }

        internal static Type GetGenericType<T>(string typeName)
        {
            Type type = Type.GetType(typeName);

            return GetGenericType<T>(type);
        }

        internal static Type GetGenericType<T>(Type type)
        {
            Type[] typeArgs = { typeof(T) };

            return type.MakeGenericType(typeArgs);
        }

        internal static object InvokeMethod(object obj, string methodName, object[] parameters)
        {
            Type type = obj.GetType();
            MethodInfo method = type.GetMethod(methodName);

            return method.Invoke(obj, parameters);
        }

        internal static object InvokeMethod(object obj, string methodName, string[] signature, object[] parameters)
        {
            Type type = obj.GetType();
            IEnumerable<MethodInfo> methods = type.GetMethods().Where(m => m.Name == methodName);
            MethodInfo method = FindBySignature(methods, signature);

            if (method == null)
            {
                throw new ReflectionException("Could not found method with signature: " + methodName + "(" + string.Join(", ", signature) + ")");
            }

            return method.Invoke(obj, parameters);
        }

        internal static object InvokeMethod(object obj, string methodName, Type[] signature, object[] parameters)
        {
            Type type = obj.GetType();
            MethodInfo method = type.GetMethod(methodName, signature);

            if (method == null)
            {
                throw new ReflectionException("Could not found method with signature: " + methodName);
            }

            return method.Invoke(obj, parameters);
        }

        internal static object InvokeMethod<T>(object obj, string methodName, object[] parameters)
        {
            Type type = obj.GetType();
            MethodInfo method = type.GetMethod(methodName);
            MethodInfo generic = method.MakeGenericMethod(typeof(T));

            return generic.Invoke(obj, parameters);
        }

        internal static object InvokeMethod<T>(object obj, string methodName, string[] signature, object[] parameters)
        {
            Type type = obj.GetType();
            IEnumerable<MethodInfo> methods = type.GetMethods().Where(m => m.Name == methodName);
            MethodInfo method = FindBySignature(methods, signature);

            if (method == null)
            {
                throw new ReflectionException("Could not found method with signature: " + methodName + "(" + string.Join(", ", signature) + ")");
            }

            MethodInfo generic = method.MakeGenericMethod(typeof(T));

            return generic.Invoke(obj, parameters);
        }

        internal static object InvokeStaticMethod(string typeName, string methodName, object[] parameters)
        {
            // Grabbing the type that has the static generic method
            Type type = Type.GetType(typeName);

            return InvokeStaticMethod(type, methodName, parameters);
        }

        internal static object InvokeStaticMethod(Type type, string methodName, object[] parameters)
        {
            // Grabbing the specific static method
            MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);

            // Simply invoking the method and passing parameters
            // The null parameter is the object to call the method from. Since the method is
            // static, pass null.
            return method.Invoke(null, parameters);
        }

        internal static object InvokeStaticMethod<T>(string typeName, string methodName, object[] parameters)
        {
            // Grabbing the type that has the static generic method
            Type type = Type.GetType(typeName);

            return InvokeStaticMethod<T>(type, methodName, parameters);
        }

        internal static object InvokeStaticMethod<T>(Type type, string methodName, object[] parameters)
        {
            // Grabbing the specific static method
            MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);

            // Binding the method info to generic arguments
            MethodInfo genericMethod = method.MakeGenericMethod(typeof(T));

            // Simply invoking the method and passing parameters
            // The null parameter is the object to call the method from. Since the method is
            // static, pass null.
            return genericMethod.Invoke(null, parameters);
        }
        
        internal static object InvokeStaticMethod<T>(Type type, string methodName, string[] signature, object[] parameters)
        {
            // Grabbing the specific static method
            IEnumerable<MethodInfo> methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == methodName);
            MethodInfo method = FindBySignature(methods, signature);

            // Binding the method info to generic arguments
            MethodInfo genericMethod = method.MakeGenericMethod(typeof(T));

            // Simply invoking the method and passing parameters
            // The null parameter is the object to call the method from. Since the method is
            // static, pass null.
            return genericMethod.Invoke(null, parameters);
        }

        internal static object InvokeStaticMethodFromGenericType(Type type, string methodName, object[] parameters)
        {
            // Grabbing the specific static method
            MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);

            // Simply invoking the method and passing parameters
            // The null parameter is the object to call the method from. Since the method is
            // static, pass null.
            return method.Invoke(null, parameters);
        }

        internal static object InvokeStaticMethodFromGenericType(Type type, string methodName, string[] signature, object[] parameters)
        {
            // Grabbing the specific static method
            IEnumerable<MethodInfo> methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == methodName);
            MethodInfo method = FindBySignature(methods, signature);

            if (method == null)
            {
                throw new ReflectionException("Could not found method with signature: " + methodName + "(" + string.Join(", ", signature) + ")");
            }

            // Simply invoking the method and passing parameters
            // The null parameter is the object to call the method from. Since the method is
            // static, pass null.
            return method.Invoke(null, parameters);
        }

        internal static T GetProperty<T>(object obj, string propertyName)
        {
            Type type = obj.GetType();
            MethodInfo method = type.GetProperty(propertyName, typeof(T)).GetGetMethod();

            return (T)method.Invoke(obj, null);
        }

        internal static T SetProperty<T>(object obj, string propertyName, T value)
        {
            Type type = obj.GetType();
            MethodInfo method = type.GetProperty(propertyName, typeof(T)).GetSetMethod();

            return (T)method.Invoke(obj, new object[] { value });
        }

        private static MethodInfo FindBySignature(IEnumerable<MethodInfo> methods, string[] signature)
        {
            if (signature == null)
            {
                signature = new string[] { };
            }

            foreach (MethodInfo method in methods)
            {
                bool found = true;
                ParameterInfo[] parameters = method.GetParameters();

                if (parameters.Count() != signature.Count())
                {
                    found = false;
                }
                else if (parameters.Count() == 0 && signature.Count() == 0)
                {
                    // ReSharper disable RedundantAssignment
                    found = true; // Parameterless method
                    // ReSharper restore RedundantAssignment
                }
                else
                {
                    for (int n = 0; n < parameters.Count(); n++)
                    {
                        string parameterName = parameters[n].ParameterType.ToString();

                        if (!parameterName.StartsWith(signature[n]))
                        {
                            found = false;
                            break;
                        }
                    }
                }

                if (found)
                {
                    return method;
                }
            }

            return null;
        }

        internal static bool IsGenericList(this Type type)
        {
            foreach (Type currentInterface in type.GetInterfaces())
            {
                if (currentInterface.IsGenericType)
                {
                    if (currentInterface.GetGenericTypeDefinition() == typeof(ICollection<>))
                    {
                        return true;
                    }
                }
            }
            
            return false;
        }

        internal static Type GenericListType(this Type type)
        {
            foreach (Type currentInterface in type.GetInterfaces())
            {
                if (currentInterface.IsGenericType)
                {
                    if (currentInterface.GetGenericTypeDefinition() == typeof(ICollection<>))
                    {
                        return currentInterface.GetGenericArguments()[0];
                    }
                }
            }

            return null;
        }
    }
}